package com.syf.core.actuator;

import cn.hutool.core.util.StrUtil;
import com.syf.core.actuator.config.SyServiceActuatorConfiguration;
import com.syf.core.config.SyApplicationConfiguration;
import com.syf.core.engine.SyFrameContext;
import com.syf.core.exception.ServiceNotFoundException;
import com.syf.core.kit.resource.KitResourceReleaseController;
import com.syf.core.service.SyService;
import com.syf.core.service.SyServiceDefinition;
import com.syf.core.service.context.ServiceRequest;
import com.syf.core.service.context.ServiceResponse;
import com.syf.core.service.enums.ResponseFormatEnums;
import com.syf.core.service.invoke.ServiceInvokeCallback;
import com.syf.core.service.invoke.SyServiceInvoker;
import com.syf.core.service.run.DefaultServiceExecutionChainFactory;
import com.syf.core.service.run.SyServiceCallable;
import com.syf.core.service.run.SyServiceCallback;
import com.syf.core.service.run.chain.ServiceExecutionChain;
import com.syf.core.service.run.chain.ServiceExecutionChainFactory;
import com.syf.kit.SyfJsonMapper;
import org.springframework.scheduling.concurrent.CustomizableThreadFactory;

import java.util.concurrent.*;

public class SyServiceActuator implements SyServiceInvoker {

    private SyServiceActuatorConfiguration actuatorConfig;

    private SyFrameContext frameContext;

    private SyServiceContainer serviceContainer;

    private ThreadPoolExecutor executor;

    private ServiceExecutionChainFactory executionChainFactory;

    private final KitResourceReleaseController kitResourceReleaseController;

    private SyfJsonMapper jsonMapper;

    public SyServiceActuator(SyServiceActuatorConfiguration configuration, SyFrameContext frameContext) {
        this.kitResourceReleaseController = KitResourceReleaseController.getInstance();
        this.actuatorConfig = configuration;
        this.serviceContainer = new SyServiceContainer(configuration.getLazyMode(), SyApplicationConfiguration.getAppBasePackage());
        // 将单例的service对象实例化好
        serviceContainer.initSingleServices();

        this.frameContext = frameContext;

        // 初始化执行Service的线程池
        executor = new ThreadPoolExecutor(actuatorConfig.getThreadPoolConfig().getCorePoolSize(), actuatorConfig.getThreadPoolConfig().getMaximumPoolSize(), actuatorConfig.getThreadPoolConfig().getKeepAliveSeconds(), TimeUnit.SECONDS, new ArrayBlockingQueue<>(actuatorConfig.getThreadPoolConfig().getTaskQueueSize()), new CustomizableThreadFactory(actuatorConfig.getThreadPoolConfig().getThreadNamePrefix()));

        // 初始化service执行chain工厂，建立service的前后处理机制
        executionChainFactory = new DefaultServiceExecutionChainFactory(SyApplicationConfiguration.getAppBasePackage());

    }

    @Override
    public boolean isExistService(String serviceCode) {
        return serviceContainer.getServiceDefinition(serviceCode) != null;
    }

    @Override
    public boolean isExistService(Class<? extends SyService> serviceClass) {
        return serviceContainer.getServiceDefinition(serviceClass) != null;
    }

    @Override
    public SyServiceDefinition getDefinition(String serviceCode) {
        return serviceContainer.getServiceDefinition(serviceCode);
    }

    @Override
    public SyServiceDefinition getDefinition(Class<? extends SyService> serviceClass) {
        return serviceContainer.getServiceDefinition(serviceClass);
    }

    @Override
    public ServiceRequest buildServiceRequest(SyServiceDefinition definition, String callingAppComponent, String reqDataJson) throws ServiceNotFoundException {
        if (definition == null) {
            throw new ServiceNotFoundException();
        }

        Object reqData = jsonMapper.toBean(reqDataJson, definition.getRequestClass());
        ServiceRequest<Object> serviceRequest = new ServiceRequest<>(callingAppComponent, reqData);
        serviceRequest.getHead().setServiceDefinition(definition);
        return serviceRequest;
    }

    @Override
    public void invokeService(SyServiceDefinition definition, ServiceRequest request, ServiceInvokeCallback<ServiceResponse> callback) throws ServiceNotFoundException {
        Future<ServiceResponse> future = invokeService(definition, request);
        callbackHandle(future, callback);
    }

    @Override
    public Future<ServiceResponse> invokeService(SyServiceDefinition definition, ServiceRequest request) throws ServiceNotFoundException {
        if (definition == null) {
            throw new ServiceNotFoundException();
        }

        // 设置service definition
        request.getHead().setServiceDefinition(definition);

        // 获取交易执行链
        ServiceExecutionChain serviceExecutionChain = executionChainFactory.getServiceExecutionChain(definition.isSingle(), serviceContainer.getServiceInstance(definition));

        // 执行交易
        return executor.submit(new SyServiceCallable(request, serviceExecutionChain, frameContext, kitResourceReleaseController));
    }

    @Override
    public void asyncInvokeService(SyServiceDefinition definition, ServiceRequest request, ServiceInvokeCallback<ServiceResponse> callback) throws ServiceNotFoundException {
        if (definition == null) {
            throw new ServiceNotFoundException();
        }
        // 设置service definition
        request.getHead().setServiceDefinition(definition);

        // 获取交易执行链
        ServiceExecutionChain serviceExecutionChain = executionChainFactory.getServiceExecutionChain(definition.isSingle(), serviceContainer.getServiceInstance(definition));

        // 执行交易，交易执行线程内回调，而不是本线程
        executor.submit(new SyServiceCallback(request, serviceExecutionChain, frameContext, kitResourceReleaseController, callback));
    }

    @Override
    public Integer getActuatorMaxThreadNum() {
        return actuatorConfig.getThreadPoolConfig().getMaximumPoolSize();
    }

    /**
     * 同步处理Future，完成对应的回调动作
     *
     * @param responseFuture
     * @param callback       回调
     */
    private void callbackHandle(Future<ServiceResponse> responseFuture, ServiceInvokeCallback<ServiceResponse> callback) {
        try {
            ServiceResponse serviceResponse = responseFuture.get(actuatorConfig.getServiceTimeoutSends(), TimeUnit.SECONDS);

            if (StrUtil.equals(serviceResponse.getRespCode(), ResponseFormatEnums.SUCCESS.getRespCode())) {
                callback.onSuccess(serviceResponse);
            } else {
                callback.onFailure(serviceResponse);
            }
        } catch (InterruptedException | ExecutionException e) {
            callback.throwable(e);
        } catch (TimeoutException e) {
            callback.onFailure(new ServiceResponse(ResponseFormatEnums.REQUEST_PROCESS_TIMEOUT));
        }
    }
}
